---
title: Writing tests for backend
description: Define model factories and test GraphQL API
---
import ProjectName from '../../shared/components/ProjectName.component';

Pytest is a powerful testing framework for Python, and it is the recommended testing framework for the <ProjectName/> backend.
Official documentation can be found [here](https://docs.pytest.org/en/7.1.x/contents.html).
The following model will be used to provide a base for examples in this article.

```python title="packages/backend/apps/store/models.py" showLineNumbers
import hashid_field
from django.db import models


class Product(models.Model):
    id = hashid_field.HashidAutoField(primary_key=True)
    name = models.CharField(max_length=255)
```

:::tip
See the ["How to create a new Django app and model in back-end?"](../guides/backend/backend-model) is you've missed it.
:::

## Creating Django model factories

### Define factories

Inside `store/tests` create `factories.py` file and declare the following factory for our model.

```python title="packages/backend/apps/store/tests/factories.py" showLineNumbers
import factory
from ..models import Product


class ProductFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = Product

    name = factory.Faker('name')
```

The `ProductFactory` extends the `factory.django.DjangoModelFactory` class and specifies the `Product` model as the target model.
The `Meta` class contains the reference to the model being used. The `name` attribute is set to a fake product name generated by the `factory.Faker` method.

Using factories, you can easily create test data for your models in a concise and efficient manner.

:::info
Factory Boy official documentation can be found [here](https://factoryboy.readthedocs.io/en/stable/index.html).
:::


### Register factories as pytest fixtures

```python title="packages/backend/apps/store/tests/fixtures.py" showLineNumbers
import pytest_factoryboy
from . import factories


pytest_factoryboy.register(factories.ProductFactory)
```

In the provided example, we have imported the `pytest_factoryboy` library and registered the `ProductFactory`.

The `pytest_factoryboy.register` method is used to register a Factory Boy factory with Pytest. This allows you to use the factory as a fixture in your tests.

To make those factories available globally across all modules, we need to register the newly created `fixtures` module inside the global `conftest.py`.

```python title="packages/backend/conftest.py" showLineNumbers
pytest_plugins = [
    # ...
    'apps.store.tests.fixtures',
]
```

The `pytest_plugins` variable is used in Pytest to specify additional plugins to be loaded during testing.
Fixtures are a way to provide test data or a test environment to one or more tests.
They can be used to set up the state of the application before running a test or to provide a specific input to a test.
By including the `apps.store.tests.fixtures` module in the `pytest_plugins` variable, the fixtures defined in that module will be available to all tests in the project.

## How to test Queries

For the sake of the following examples, let's define a `DjangoObjectType` and `Connection` for our `Product` model.
Next, create two queries: one to list all the products and the other to fetch a single product by it's id.

:::tip
More information on `DjangoObjectType` and `Connection` can be found in ["How to add a new mutation to back-end API?"](../graphql/backend/adding-new-mutation/#define-graphql-type-and-connection)
:::

```python title="packages/backend/apps/store/schema.py" showLineNumbers
import graphene
from graphene_django import DjangoObjectType

from . import models


class ProductType(DjangoObjectType):
    class Meta:
        model = models.Product
        interfaces = (graphene.relay.Node,)
        fields = "__all__"


class ProductConnection(graphene.Connection):
    class Meta:
        node = ProductType


class Query(graphene.ObjectType):
    product = graphene.Field(ProductType)
    all_products = graphene.relay.ConnectionField(ProductConnection)

    @staticmethod
    def resolve_all_products(root, info, **kwargs):
        return models.Product.objects.all()
```

### Test `product` query

Inside `store/tests/test_schema.py` define the following class:

```python title="packages/backend/apps/store/tests/test_schema.py" showLineNumbers
import pytest

pytestmark = pytest.mark.django_db


class TestProductQuery:
    PRODUCT_QUERY = """
        query($id: ID!)  {
          product(id: $id) {
            id
            name
          }
        }
    """
```

The `TestProductQuery` class is a test case class that is used to test the GraphQL `product` query for a specific product ID.
The `PRODUCT_QUERY` constant is a string that defines the GraphQL query that will be sent to the server during testing.

The query takes a single argument, `id`, which is of type `ID!` (a non-null ID). This argument is used to specify the ID of the product that should be retrieved.
The `product` field returns the ID and name of the specified product.

During testing, the `PRODUCT_QUERY` is executed by sending a request to the GraphQL server with the `id` variable set to a specific product ID.
The response from the server is then checked to ensure that it includes the expected product ID and name.

The `test_return_product` method is a test case that verifies whether the GraphQL API correctly returns a `Product` object when queried with a valid product ID and an authorized user:

```python title="packages/backend/apps/store/tests/test_schema.py" showLineNumbers
import pytest
from graphql_relay import to_global_id


pytestmark = pytest.mark.django_db


class TestProductQuery:
    PRODUCT_QUERY = """
        query($id: ID!)  {
          product(id: $id) {
            id
            name
          }
        }
    """

    def test_return_product(self, graphene_client, product, user):
        product_global_id = to_global_id("ProductType", str(product.id))

        graphene_client.force_authenticate(user)
        executed = graphene_client.query(
            self.PRODUCT_QUERY,
            variable_values={"id": product_global_id},
        )

        assert executed == {
            "data": {
                "product": {
                    "id": product_global_id,
                    "name": product.name,
                }
            }
        }
```

The test first generates a global ID for an existing product using the `to_global_id` function from the `graphql_relay` module.
This global ID is then used as the value for the `id` variable in the `PRODUCT_QUERY`.

Then, a user is authenticated using the `graphene_client.force_authenticate` method, simulating a logged-in user trying to query for a specific product.

The `graphene_client.query` method is used to execute the GraphQL query with the existing product's global ID as the value for the `id` variable.

Finally, the test asserts that the response from the server includes the expected data for the queried product, including the product's ID and name.

### Test `allProducts` query

The `TestAllProductsQuery` class is a test case that verifies whether the GraphQL API correctly returns a list of all products when queried with the `allProducts` query.

The `ALL_PRODUCTS_QUERY` query does not take any arguments and simply returns a list of all products in the database.
The `allProducts` field returns a connection type, which includes a list of edges. Each edge contains a node that represents a product in the list.
The node includes the product's ID and name.

The `test_return_all_products` method is a test case that verifies whether the GraphQL API correctly returns a list of all products when queried with the `allProducts` query.

```python title="packages/backend/apps/store/tests/test_schema.py" showLineNumbers
import pytest
from graphql_relay import to_global_id

pytestmark = pytest.mark.django_db


class TestAllProductsQuery:
    ALL_PRODUCTS_QUERY = """
        query  {
          allProducts {
            edges {
              node {
                id
                name
              }
            }
          }
        }
    """

    def test_return_all_products(self, graphene_client, product_factory, user):
        products = product_factory.create_batch(3)

        graphene_client.force_authenticate(user)
        executed = graphene_client.query(self.ALL_PRODUCTS_QUERY)

        assert executed == {
            "data": {
                "allProducts": {
                    "edges": [
                        {
                            "node": {
                                "id": to_global_id("ProductType", str(product.id)),
                                "name": product.name,
                            }
                        }
                        for product in products
                    ]
                }
            }
        }
```

First, the test generates three products using the `product_factory.create_batch` method from the `factory_boy` library.
The `create_batch` method is used to generate multiple `Product` objects in the database.

Then, a user is authenticated using the `graphene_client.force_authenticate` method, simulating a logged-in user trying to query for all products.

The `graphene_client.query` method is used to execute the GraphQL `allProducts` query.

Finally, the test asserts that the response from the server includes the expected list of products, including the ID and name of each product.

The test achieves this by constructing a list comprehension that generates a list of edges, where each edge contains a node representing a single product.
The expected output is a dictionary with the same structure as the server response.


## How to test Mutations

Let's define the following serializer to work with our mutations:

```python title="packages/backend/apps/store/serializers.py" showLineNumbers
from hashid_field import rest as hidrest
from rest_framework import serializers


class ProductSerializer(serializers.ModelSerializer):
    id = hidrest.HashidSerializerCharField(source_field="store.Product.id", read_only=True)

    class Meta:
        model = models.Product
        fields = ('id', 'name',)
```

:::tip
Detailed description on how to work with serializers can be found in ["Working with serializers"](../graphql/backend/working-with-serializers)
:::

Next, define the following schema:

```python title="packages/backend/apps/store/schema.py" showLineNumbers
import graphene
from graphene_django import DjangoObjectType
from common.graphql import mutations
from . import models, serializers


class ProductType(DjangoObjectType):
    class Meta:
        model = models.Product
        interfaces = (graphene.relay.Node,)
        fields = "__all__"


class ProductConnection(graphene.Connection):
    class Meta:
        node = ProductType


class CreateProductMutation(mutations.CreateModelMutation):
    class Meta:
        serializer_class = serializers.ProductSerializer
        edge_class = ProductConnection.Edge


class UpdateProductMutation(mutations.UpdateModelMutation):
    class Meta:
        serializer_class = serializers.ProductSerializer
        edge_class = ProductConnection.Edge


class DeleteProductMutation(mutations.DeleteModelMutation):
    class Meta:
        model = models.Product


class Mutation(graphene.ObjectType):
    create_product = CreateProductMutation.Field()
    update_product = UpdateProductMutation.Field()
    delete_product = DeleteProductMutation.Field()
```

:::tip
Detailed description on how to work with mutations can be found in ["How to add a new mutation to back-end API?"](../graphql/backend/adding-new-mutation/#define-graphql-type-and-connection)
:::

### Test `createProduct`

`TestCreateProductMutation` is a Python class that represents a GraphQL mutation for creating a new product.

The mutation is defined as a string constant called `CREATE_MUTATION`.
It uses the GraphQL syntax to define a `createProduct` mutation that takes an input of type `CreateProductMutationInput`.

The `createProduct` mutation returns a `product` object that contains the `id` and `name` of the newly created product.

```python title="packages/backend/apps/store/tests/test_schema.py" showLineNumbers
import pytest
from graphql_relay import from_global_id
from .. import models

pytestmark = pytest.mark.django_db


class TestCreateProductMutation:
    CREATE_MUTATION = """
        mutation($input: CreateProductMutationInput!)  {
          createProduct(input: $input) {
            product {
              id
              name
            }
          }
        }
    """

    def test_create_new_product(self, graphene_client, user):
        input_data = {"name": "Product"}

        graphene_client.force_authenticate(user)
        executed = graphene_client.mutate(
            self.CREATE_MUTATION,
            variable_values={"input": input_data},
        )

        assert executed["data"]["createProduct"]
        assert executed["data"]["createProduct"]["product"]
        assert executed["data"]["createProduct"]["product"]["name"] == input_data["name"]

        product_global_id = executed["data"]["createCrudDemoItem"]["crudDemoItem"]["id"]
        _, pk = from_global_id(product_global_id)
        product = models.Product.objects.get(pk=pk)

        assert product.name == input_data["name"]
```

The `test_create_new_product` first creates an input data dictionary with a `name` key and a value of `"Product"`.
Then it authenticates the client using the `graphene_client.force_authenticate` method with the `user` fixture.

Finally, it executes the mutation using the `graphene_client.mutate` method with the `CREATE_MUTATION` and `input_data` as the `variable_values`.
The response of the mutation is then tested using three assertions.
The first assertion checks if the mutation returned any data at all.
The second assertion checks if the mutation returned a product object.
The third assertion checks if the name of the returned product object matches the name of the input data.

The next section of the test retrieves the created product from the database using the `from_global_id` method to get the primary key of the product from the global ID returned by the mutation.
It then uses the primary key to get the product object from the database.

Finally, it tests that the name of the product object retrieved from the database matches the name of the input data dictionary used to create the product in the mutation.


### Test `updateProduct`

The `UPDATE_MUTATION` is a GraphQL mutation that updates an existing product with the given input data.
It takes an input object of type `UpdateProductMutationInput` and returns the ID and name of the updated product.

```python title="packages/backend/apps/store/tests/test_schema.py" showLineNumbers
import pytest
from graphql_relay import to_global_id

pytestmark = pytest.mark.django_db


class TestUpdateProductMutation:
    UPDATE_MUTATION = """
        mutation($input: UpdateProductMutationInput!)  {
          updateProduct(input: $input) {
            product {
              id
              name
            }
          }
        }
    """

    def test_update_product(self, graphene_client, user, product):
        input_data = {
            "id": to_global_id("ProductType", str(product.id)),
            "name": "New item name",
        }

        graphene_client.force_authenticate(user)
        executed = graphene_client.mutate(
            self.UPDATE_MUTATION,
            variable_values={"input": input_data},
        )
        product.refresh_from_db()

        assert executed["data"]["updateProduct"]
        assert executed["data"]["createProduct"]["product"]
        assert executed["data"]["updateProduct"]["product"]["name"] == input_data["name"]
        assert product.name == input_data["name"]
```

The `test_update_product` first creates an input data dictionary with an `id` key and a value of the global ID of the product to update, as well as a `name` key and a value of `"New item name"`.

Then it authenticates the client using the `graphene_client.force_authenticate` method with the `user` fixture.

The mutation is executed using the `graphene_client.mutate` method with the `UPDATE_MUTATION` and `input_data` as the `variable_values`.

After the mutation is executed, the product object is refreshed from the database to ensure that the update was successful.

The response of the mutation is then tested using four assertions.
The first assertion checks if the mutation returned any data at all.
The second assertion checks if the mutation returned a product object.
The third assertion checks if the name of the returned product object matches the `"New item name"` value of the input data dictionary used to update the product in the mutation.
The fourth assertion checks if the name of the product object retrieved from the database matches the `"New item name"` value of the input data dictionary used to update the product in the mutation.

### Test `deleteProduct`

The `DELETE_MUTATION` is a GraphQL mutation that deletes an existing product with the given input data.
It takes an input object of type `DeleteProductMutationInput` and returns the ID of the deleted product.

```python title="packages/backend/apps/store/tests/test_schema.py" showLineNumbers
import pytest
from graphql_relay import to_global_id
from .. import models

pytestmark = pytest.mark.django_db


class TestDeleteProductMutation:
    DELETE_MUTATION = """
        mutation($input: DeleteProductMutationInput!) {
          deleteProduct(input: $input) {
            deletedIds
          }
        }
    """

    def test_delete_product(self, graphene_client, user, product):
        product_global_id = to_global_id("ProductType", str(product.id))

        graphene_client.force_authenticate(user)
        executed = graphene_client.mutate(
            self.DELETE_MUTATION,
            variable_values={"input": {"id": product_global_id}},
        )

        assert executed == {"data": {"deleteProduct": {"deletedIds": [product_global_id]}}}
        assert not models.Product.objects.filter(id=product.id).exists()
```

The `test_delete_product` method first gets the global ID of the product to delete using the `to_global_id` method and the `product.id` attribute.

Then it authenticates the client using the `graphene_client.force_authenticate` method with the `user` fixture.

The mutation is executed using the `graphene_client.mutate` method with the `DELETE_MUTATION` and `product_global_id` as the `id` value of the `input` dictionary.

After the mutation is executed, the response of the mutation is tested using two assertions.
The first assertion checks if the mutation returned a dictionary with a `deletedIds` key and a value of a list with the global ID of the deleted product.
The second assertion checks if the product object no longer exists in the database.
